module iTimer.av.Ring ;

import iTimer.Types;
private import tango.core.sync.Mutex;
private import tango.time.Time;
private import tango.time.Clock;
private import tango.time.WallClock;
private import tango.stdc.stdio;

extern(C){
    void exit(int);
}

struct vAppConf{
	static const TableDef def	=
		{
			"iApp", 
			[
				{DataType.UINT,		"id",			ColType.ID },
				{DataType.UINT,		"hVersion",		ColType.NULL },
				{DataType.UINT,		"lVersion",		ColType.NULL },
				{DataType.UINT,		"dbVersion",		ColType.NULL },
			]
		};
	
	mixin DefTable!(vAppConf);
	mixin DefMask!(vAppConf,  
			"all", 	 0xfff 
		);
    
}


struct vTask {
	static const TableDef def	=
		{
			"iTask", 
			[
				{DataType.UINT,		"id",			ColType.ID },
				
				{DataType.UINT,		"iLoop",		ColType.NULL },
				{DataType.UINT,	    "iDate",		ColType.NULL },
				{DataType.UINT,	    "iDays",		ColType.NULL },
				{DataType.UINT,	    "iTime",		ColType.NULL },
				
				{DataType.UINT,		"iSkip",		ColType.NULL },
				{DataType.UINT,		"iDuration",		ColType.NULL },
				{DataType.UINT,		"iUsed",		ColType.NULL },
				{DataType.TEXT,	 "iFile",		ColType.NULL },
                
				{DataType.UINT,	    "isFire",		ColType.NULL } ,
				{DataType.UINT,	    "isToday",		ColType.NULL} ,
				{DataType.UINT,	    "isDone",		ColType.NULL } ,
				
			]
		};
	
	mixin DefTable!(vTask);
	mixin DefMask!(vTask,  
			"all", 	 0xffff
		);
    
}
alias vTask.Row_all _vTask ;
static _vTask*    win_task ;
alias RowSelect!(vTask.Row_all, "1 ORDER BY iTime ASC")	vTask_SelAll;

static TaskList Task_List ;

struct TaskList {
    static  Connection* db_con;
    private static Mutex mux ;
    private static bool is_disabled ;

    static void Init() {
        vTime.Init ;
        mux = new Mutex;
        char[] db_name		= AppConf.dir ~  AppConf.app ~ ".db3";
        db_con			= Connection.open(db_name);
        db_con.exec("PRAGMA journal_mode = MEMORY");
        
        db_con.exec(vAppConf.sql_create); 
        
        scope stmt	= Statement.Create(db_con);
        scope(exit) delete stmt;
        assert(stmt !is null);
        
        vAppConf.Row_all* _papp = new vAppConf.Row_all;

        bool is_need_update = true ;
        bool is_need_insert = true ;

        if( vAppConf.Row_all.One(stmt, _papp, 1) ) {
            assert(_papp !is null);
            if( _papp.dbVersion  is AppConf.app_dbVersion  ) {
                is_need_update  = false ;
            }
            is_need_insert  = false ;
        }  else {
            _papp   = new vAppConf.Row_all ;
        }
        
        if( is_need_update ){
            assert(_papp !is null);
            _papp.hVersion  = AppConf.app_hVersion ;
            _papp.lVersion  = AppConf.app_lVersion ;
            _papp.dbVersion  = AppConf.app_dbVersion ;
            
            if( is_need_insert ) {
                vAppConf.Row_all.Insert(stmt, _papp) ;
                assert(_papp !is null);
            } else {
                vAppConf.Row_all.Update(stmt, _papp) ;
                assert(_papp !is null);
            }
            // delete old table
            static const delete_sql = `DROP TABLE IF EXISTS iTask` ;
            db_con.exec(delete_sql); 
        }
        
        db_con.exec(vTask.sql_create); 
        vPlayer.Init ;
        Task_List.Load ;
    }
    private _vTask[]	_list	= null ;
    
    enum {
        WeekDay1    = 0b1 ,
        WeekDay2    = 0b10 ,
        WeekDay3    = 0b100 ,
        WeekDay4    = 0b1000 ,
        WeekDay5    = 0b10000 ,
        WeekDay6    = 0b100000 ,
        WeekDay0   =  0b1000000 ,
        WorkDays    = WeekDay1 | WeekDay2 | WeekDay3 | WeekDay4 | WeekDay5 
    }
    static WeekDayList  = [ WeekDay1, WeekDay2, WeekDay3, WeekDay4, WeekDay5, WeekDay6, WeekDay0 ] ;

    bool hasDay(_vTask* p, int i ) {
        if( !p.iLoop ) {
            return (WeekDayList[i] & WorkDays) !is 0 ;
        }
        if( i <0 || i >= WeekDayList.length ){
            return false ;
        }
        return (WeekDayList[i] & p.iDays) !is 0 ;
    }
    
    void setDay(_vTask* p, int i ) {
        if( i >=0 && i < WeekDayList.length ){
            p.iDays  = p.iDays | WeekDayList[i] ;
        }
    }
    
    char[] iTime(_vTask* p, vBuffer bu){
        assert(p !is null);
        assert(bu !is null);
        bu.clear;
        auto _iTime = p.iTime;
        int h    =  _iTime / 10000 ;
        _iTime  = _iTime % 10000 ;
        int m   = _iTime /100  ;
        _iTime  = _iTime % 100 ;
        int s = _iTime;
        if( h <= 9 ) {
            bu(0);
        }
        bu(h);
        bu(':');
        if( m <= 9 ) {
            bu(0);
        }
        bu(m);
        bu(':');
        if( s <= 9 ) {
            bu(0);
        }
        bu(s);
        
        return cast(char[]) bu.slice  ;
    }
    
    char[] iDate(_vTask* p, vBuffer bu){
        assert(p !is null);
        assert(bu !is null);
        bu.clear;
        if( p.iLoop ) {
            return "" ;
        }
        auto _iTime = p.iDate;
        int year    =  _iTime / 10000 ;
        _iTime  = _iTime % 10000 ;
        int month   = _iTime /100  ;
        _iTime  = _iTime % 100 ;
        int day = _iTime;
        bu(year)('/')(month)('/')(day);
        return cast(char[]) bu.slice  ;
    }
    
    void Disable() {
        mux.lock();
        is_disabled   = true ;
        mux.unlock();
    }
    
    void Enable() {
        mux.lock();
        is_disabled   = false ;
        mux.unlock();
    }
    
    size_t Length(){
        int len ;
        mux.lock();
        len = _list.length ;
        mux.unlock();
        return len ;
    }
    
	void Load( bool skip_lock = false, bool reload = false ) {
		if( !skip_lock ) mux.lock() ;
        
            vTime   it   = vTime.now  ;
        
            if( !reload && _list.length !is 0  ) {
                return ;
            }
            
            _list.length    = 0 ;

			try {
				scope stmt2	= Statement.Create(db_con);
				int len     = vTask_SelAll.count(stmt2);
				_list       = new _vTask[len] ;
				
				vTask_SelAll.select(stmt2) ;
                
                auto _curent_task_id    = vPlayer.getTaskId() ;
                
                version(DEV){
                    vLog.log!(__FILE__,__LINE__)("TaskList.Load(skip_lock={},reload={})", skip_lock, reload);
                }
                foreach(int i, ref p; _list){
                    vTask_SelAll.fetchOne(stmt2, &p ) ;
                    p.isDone   = p.iTime < it.time ;
                    if( p.iUsed ) {
                        p.isToday    = false ;
                    } else {
                        if( p.iLoop ) { // week
                            p.isToday    = hasDay(&p, it.dow );
                        } else { // day 
                            p.isToday    = p.iDate is it.date ;
                        }
                    }
                    p.isFire    = _curent_task_id is p.id ;
                    version(DEV){
                        vLog.log!(__FILE__,__LINE__)("id={}, isDone={}, isToday={}, isFire={} iTime={}, iDate={}  iDays={:b8} iUsed={}  iFile={} iLoop={} iSkip={} iDuration={}",  p.id, p.isDone, p.isToday , p.isFire, p.iTime, p.iDate, p.iDays, p.iUsed, p.iFile, p.iLoop, p.iSkip, p.iDuration );
                    }
                }
				delete	stmt2;
			}catch(Exception e){
				version(DEV) log!(__FILE__, __LINE__)( "throw: {}\n",  e.msg);
			}
		if( !skip_lock ) mux.unlock();
	}
    
    _vTask* New() {
        auto p      = new _vTask ;
        p.iDays     = WorkDays ;
        p.iLoop     = 1 ;
        return p ;
    }
    
	_vTask* ByID(int id, bool skip_lock = false ) {
        if( !skip_lock ) mux.lock();
		if( _list is null ){
            if( !skip_lock ) mux.unlock();
			return null;
		}
		foreach( ref p ;  _list ){
			if( p.id is id ){
                if( !skip_lock ) mux.unlock();
				return &p ;
			}
		}
        if( !skip_lock ) mux.unlock();
		return null;
	}
    
    void Save(_vTask* p){
        mux.lock();
        assert(p !is null);
        scope stmt	= Statement.Create(db_con);
        scope(exit) delete stmt;
        if( p.id <= 0 ) {
            _vTask.Insert(stmt, p);
        } else {
            _vTask.Update(stmt, p);
        }
        Task_List.Load(true, true) ;
        mux.unlock();
    }
    
    void Delete(int id){
        mux.lock();
        assert(id > 0);
        scope stmt	= Statement.Create(db_con);
        scope(exit) delete stmt;
        _vTask.Delete(stmt, id);
        Load(true, true);
        mux.unlock();
    }
    

    int opApply (int delegate(ref int i, ref _vTask*) dg)
    {
		mux.lock();
            if( _list is null || is_disabled ) {
		        mux.unlock();
                return 0 ;
            }
            int result  = 0;
            int length  = _list.length ;
            
            for (int i=0; i < length; ++i) {
                _vTask* _ptr    = & _list[i] ;
                result  = dg(i, _ptr) ;
                if( result !is 0 ) {
                    break ;
                }
            }
		mux.unlock();
        return result;
    }

    static void update_task() {
        
        Sys.PreventPowerdown();
        
		mux.lock();
        
        vTime   it   = vTime.now  ;
        bool reloaded   = false ;
        
        if( vTime.is_day_changed(&it) ) {
            version(DEV) log!(__FILE__,__LINE__)("day passed");
            reloaded    = true ;
        }
        
        if( vTime.has_adjust_clock ) {
            reloaded    = true ;
            version(DEV) log!(__FILE__,__LINE__)("has_adjust_time");
        }
        
        if( it.time % 10000 is 5959 ) {
            version(DEV) log!(__FILE__,__LINE__)("hour passed");
            // reloaded    = true ;
        }
        
        if( reloaded ){
            Task_List.Load(true, true);
            GShell.getDisplay().timerExec (1 ,  iTaskList.async_reload ) ;
        }
		mux.unlock();
        
        
        foreach(int i ,_vTask* _ptr;  Task_List ) {
            // version(DEV)  if( reloaded ) log!(__FILE__,__LINE__)("reload task_id={} isToday={} iTime={} isFire={}",  _ptr.id, _ptr.isToday , _ptr.iTime ,  _ptr.isFire );
            
            int  time_diff  =  it.time - _ptr.iTime ;
            
            if(  time_diff < 0 ) {
                // not ready
                continue ;
            } else{
                // passed 
                if( time_diff > 10 && !_ptr.isFire ) {
                    _ptr.isDone = true ;
                }
            }
            
            bool is_on_fire = time_diff >= 0 && time_diff <= 1 ;

            if( !_ptr.isToday ) {
               version(DEV) {
                   if( is_on_fire ) {
                    log!(__FILE__,__LINE__)("{} pass by isToday", _ptr.id );
                   }
                }
                continue ;
            }
            
            if( _ptr.isFire ) {
               version(DEV) {
                   if( is_on_fire ) {
                        log!(__FILE__,__LINE__)("{} pass by isFire", _ptr.id );
                   }
                }
                continue ;
            }
            
            if( !is_on_fire ) {
                continue ;
            }
            
            version(DEV) log!(__FILE__,__LINE__)("update_task try play task_id={} ",  _ptr.id);
               
            if( !vPlayer.play( _ptr.iFile, _ptr.iSkip, _ptr.iDuration,  _ptr.id,  true ) ) {
                iTaskList.list(_ptr.id);
                AppWin.bottom_msg_label.setText( vPlayer.ip.ErrorMessage );
            } 
            break;
        }
    }
}

struct iPlayer {
    alias typeof(this) This;
    alias This* pThis;
    private {
        static bool is_mpg123_inited ;
        static char[] _error_message ;
    }
    enum Status {
        Closed ,
        Stoped ,
        Playing ,
        Paused ,
    }
    private {
        mpg123_handle*   decoder = null ;
        size_t      buffer_size , block_size , total,  played , _length ;
        c_int       channels;
        c_int       encoding_size ;
        c_long     rate ;
        char[]      file ;
        char[]      err_msg ;
        HWAVEOUT        _ghWaveOut = null ;
        WAVEHDR[2]      WaveHdr ;
        ubyte[][2]   _bu ;
        Mutex   mux ;

        c_double   current_seconds ;
        c_double   seconds_left;
        c_double   total_seconds ;
        
        ptrdiff_t   current_frame;
        ptrdiff_t   frames_left;
        ulong      frame_counter ;
        bool        already_decode ;
        bool        force_stoped ;
        Status     stat ;
    }
    
    static bool Init() {
        if( !is_mpg123_inited ) {
            auto ret = mpg123_init();
            if(ret !is MPG123_OK) {
                _error_message = cast(char[]) fromStringz(mpg123_plain_strerror(ret)) ;
                return  false;
            }
            is_mpg123_inited    = true ;
        }
        return true ;
    }
    
    static bool UnInit(){
        if( is_mpg123_inited ) {
            auto ret = mpg123_init();
            if(ret !is MPG123_OK) {
                _error_message  = fromStringz(mpg123_plain_strerror(ret)) ;
                return  false;
            }
            is_mpg123_inited    = false ;
        }
        return true ;
    }
    
    static void show_err(char[] fn, MMRESULT err){
        wchar[1024] tmp = 0 ;
        if( err !is 0 ) {
            MMRESULT ret    = waveOutGetErrorTextW(err, tmp.ptr,  tmp.length) ;
            version(DEV) log!(__FILE__,__LINE__)("{} = {}:{}  `{}`", fn , err, ret, fromString16z(tmp.ptr));
        }
    }
    
    bool Open(char[] file) {
        if( !Init ) {
            return false ;
        }
        if( mux is null ) {
            mux = new Mutex ;
        }
        Clear();
        force_stoped    = false ;
        if( !Pth.exists(file) ){
            err_msg = "File not exists.";
            return false ;
        }
        this.file   = file ;
        int err;
        mux.lock();
        scope(exit) {
            mux.unlock();
        }
        decoder = mpg123_new(null , &err) ;
        if( decoder is null || err !is MPG123_OK) {
            err_msg = cast(char[]) fromStringz(mpg123_plain_strerror(err)) ;
            return false ;
        }
        auto  ret = mpg123_open(decoder, cast(char*)toStringz(file) );
        if(ret !is MPG123_OK) {
            err_msg = cast(char[]) fromStringz(mpg123_plain_strerror(ret)) ;
            return false ;
        }
        
        c_int       encoding ;
        ret = mpg123_getformat(decoder, 
			&rate, 
			&channels,
			&encoding
		);
        if(ret !is MPG123_OK) {
            err_msg = cast(char[]) fromStringz(mpg123_plain_strerror(ret)) ;
            return false ;
        }
        assert(encoding is MPG123_ENC_SIGNED_16);
        
        block_size    = mpg123_outblock(decoder);
        if( block_size <= 0 || block_size >=  ushort.max  ) {
            err_msg = "mpg123_outblock error" ;
            return false;
        }
        encoding_size   = mpg123_encsize(encoding);
        _length = mpg123_length(decoder)  ;
        
        // log("rate={}, channels={}, encoding_size={}  length={} block_size={}", rate,  channels, encoding_size, _length, block_size);
        
        ret = mpg123_format_none(decoder);
        if( ret !is MPG123_OK ) {
            err_msg = cast(char[]) fromStringz(mpg123_plain_strerror(ret)) ;
            return false ;
        }
        
        ret = mpg123_format(decoder, rate, channels, encoding);
        if( ret !is MPG123_OK ) {
            err_msg = cast(char[]) fromStringz(mpg123_plain_strerror(ret)) ;
            return false ;
        }
        
        getPosition();
        total_seconds = seconds_left ;
        
        buffer_size = block_size * 2 ;
        _bu[0]  = new ubyte[ buffer_size ] ;
        _bu[1]  = new ubyte[ buffer_size ] ;
        assert(channels is 2);
        
        if( waveOutGetNumDevs() is 0 ) {
            err_msg = "not find sound device";
            // version(DEV) log!(__FILE__,__LINE__)("waveOutGetNumDevs: {}", err_msg );
            return false ;
        }
        
        WAVEFORMATEX outFormatex ;
        outFormatex.wFormatTag      = WAVE_FORMAT_PCM;
        outFormatex.wBitsPerSample  =  encoding_size * 8 ;
        outFormatex.nChannels           = channels ;
        outFormatex.nSamplesPerSec  =  rate;
        outFormatex.nAvgBytesPerSec = outFormatex.nSamplesPerSec * outFormatex.nChannels * outFormatex.wBitsPerSample / 8 ;
        outFormatex.nBlockAlign         = outFormatex.nChannels * outFormatex.wBitsPerSample / 8 ;
        
        if( _ghWaveOut !is null ) {
            MMRESULT ret;
            ret = waveOutReset(_ghWaveOut); 
            show_err("waveOutReset", ret);
        }
        
        MMRESULT    mmr ;
        mmr = waveOutOpen(&_ghWaveOut, WAVE_MAPPER, &outFormatex, cast(DWORD) &SpeakerCallback, cast(DWORD) this , CALLBACK_FUNCTION);
        show_err("waveOutOpen", mmr);
        
        return true ;
    }
    
    private static extern(Windows) void SpeakerCallback (HWAVEOUT _waveoutdev, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2){
        iPlayer* _this = cast(iPlayer*) dwInstance ;
        _this.mux.lock();
        scope(exit) {
            _this.mux.unlock();
        }
        switch(uMsg) {
            case WOM_DONE:
                ptrdiff_t   frame_offset    = 0 ;
                ptrdiff_t   buffered_bytes = 0 ;
                c_int ret    = mpg123_position( _this.decoder, frame_offset, buffered_bytes, &_this.current_frame, &_this.frames_left, &_this.current_seconds, &_this.seconds_left);
            
                PWAVEHDR  _ptr  = cast(PWAVEHDR) dwParam1 ;
                c_uint done ;
                ret = mpg123_read(_this.decoder,  cast(ubyte*) _ptr.lpData  , _this.buffer_size , &done) ;
                if( ret !is MPG123_OK || done >  _this.buffer_size ) {
                    _this.err_msg = cast(char[]) fromStringz(mpg123_plain_strerror(ret)) ;
                    return  ;
                }
                if( !_this.already_decode ) {
                    _this.already_decode    = true ;
                }
                if( _this.frames_left <= 1 ) {
                    _this.stat  = Status.Stoped ;
                }
                _ptr.dwBufferLength  = done ;
                ret =   waveOutWrite ( _this._ghWaveOut, _ptr, WAVEHDR.sizeof ) ;
                _this.frame_counter++;
                
                break ;
            case WOM_CLOSE:
                _this.stat  = Status.Stoped ; 
                break ;
            default:
                break ;
        }
    }
    
    double getPosition(){
        mux.lock();
        scope(exit) {
            mux.unlock();
        }
        if( decoder !is null ) {
                ptrdiff_t   frame_offset    = 0 ;
                ptrdiff_t   buffered_bytes = 0 ;
                c_int ret    = mpg123_position( decoder, frame_offset, buffered_bytes, &current_frame, &frames_left, &current_seconds, &seconds_left);
        }
        return current_seconds ;
    }
    
    bool setPosition(double pos){
        mux.lock();
        scope(exit) {
            mux.unlock();
        }
        if( decoder !is null ) {
            auto frame = mpg123_timeframe(decoder, pos );
            auto ret    = mpg123_seek_frame(decoder, frame, SEEK_SET );
            if(frame !is ret) {
                err_msg = cast(char[]) fromStringz(mpg123_plain_strerror(ret)) ;
                return false ;
            }
            return true ;
        }
        return false ;
    }
    
    DWORD getVolume(){
        mux.lock();
        scope(exit) {
            mux.unlock();
        }
        if( _ghWaveOut !is null ) {
            DWORD vol ;
            waveOutGetVolume(_ghWaveOut, &vol) ;
            return vol ;
        }
        return 0 ;
    }
    
    bool setVolume(DWORD vol){
        mux.lock();
        scope(exit) {
            mux.unlock();
        }
        if( _ghWaveOut !is null ) {
            waveOutSetVolume(_ghWaveOut, vol) ;
            return true ;
        }
        return false ;
    }
        
    bool Play(){
        mux.lock();
        scope(exit) {
            mux.unlock();
        }
        
        force_stoped    = false ;
        if( stat !is Status.Playing ) {
            
            // Header initialisieren und vorbereiten
            WaveHdr[0].lpData          = cast(char*) _bu[0].ptr ;
            WaveHdr[0].dwBufferLength  = buffer_size ;
            WaveHdr[0].dwBytesRecorded = 0 ;
            WaveHdr[0].dwUser          = 0 ;
            WaveHdr[0].dwFlags         = 0 ;
            WaveHdr[0].dwLoops         = 0 ;
            WaveHdr[0].lpNext           = null ;
            WaveHdr[0].reserved        = 0 ;

            MMRESULT ret=waveOutPrepareHeader (_ghWaveOut, & WaveHdr[0] , WAVEHDR.sizeof ) ;
            show_err ("waveOutPrepareHeader", ret);
            
            // Header initialisieren und vorbereiten
            WaveHdr[1].lpData          = cast(char*) _bu[1].ptr ;
            WaveHdr[1].dwBufferLength  = buffer_size ;
            WaveHdr[1].dwBytesRecorded = 0 ;
            WaveHdr[1].dwUser          = 0 ;
            WaveHdr[1].dwFlags         = 0 ;
            WaveHdr[1].dwLoops         = 0 ;
            WaveHdr[1].lpNext           = null ;
            WaveHdr[1].reserved        = 0 ;
            
            ret=waveOutPrepareHeader (_ghWaveOut, & WaveHdr[1] , WAVEHDR.sizeof ) ;
            show_err ("waveOutPrepareHeader", ret);
            
            c_uint done ;
            ret = mpg123_read(decoder, _bu[0].ptr  , buffer_size , &done);
            if( ret !is MPG123_OK || done > buffer_size ) {
                err_msg = cast(char[]) fromStringz(mpg123_plain_strerror(ret)) ;
                return false ;
            }
            WaveHdr[1].dwBufferLength  = done ;
            ret =   waveOutWrite (_ghWaveOut, & WaveHdr[0], WAVEHDR.sizeof ) ;
            
            ret = mpg123_read(decoder, _bu[1].ptr  , buffer_size , &done);
            if( ret !is MPG123_OK || done > buffer_size ) {
                err_msg = cast(char[]) fromStringz(mpg123_plain_strerror(ret)) ;
                return false ;
            }
            WaveHdr[1].dwBufferLength  = done ;
            ret =   waveOutWrite (_ghWaveOut, & WaveHdr[1], WAVEHDR.sizeof ) ;
            
            stat    = Status.Playing ;
        }
        return true ;
    }
    
    bool isStoped() {
        mux.lock();
        scope(exit) {
            mux.unlock();
        }
        if( force_stoped ) {
            force_stoped    = false ;
            auto ret =waveOutClose(_ghWaveOut) ;  show_err ("waveOutClose", ret);
            // auto ret = waveOutReset(_ghWaveOut) ;  show_err ("waveOutReset", ret);
            return true ;
        }
        if(  stat  is Status.Playing ) {
            if( already_decode && frames_left <= 1 ) {
                stat    = Status.Stoped ;
                return true ;
            }
            return false ;
        }
        return true ;
    }
    
    void Close() {
        if(mux !is null) mux.lock();
        scope(exit) {
            if(mux !is null) mux.unlock();
        }
        if( _ghWaveOut !is null ) {
            MMRESULT ret ;
            ret = waveOutBreakLoop(_ghWaveOut) ;
            show_err ("waveOutBreakLoop", ret);
            // ret =waveOutClose(_ghWaveOut) ;
           // show_err ("waveOutClose", ret);
        }
        if( decoder !is null ) {
            mpg123_close(decoder);
            mpg123_delete(decoder);
            decoder = null ;
        }
        current_seconds = 0 ;
        seconds_left = 0 ;
        total_seconds   = 0 ;
        frame_counter   = 0 ;
        already_decode  = false ;
        force_stoped    = true ;
        stat    = Status.Closed ;
    }
    alias Close Stop;
    
    bool Pause(){
        mux.lock();
        scope(exit) {
            mux.unlock();
        }
        if( _ghWaveOut !is null ) {
            waveOutPause(_ghWaveOut);
        }
        stat    = Status.Paused ;
        return true ;
    }
    
    void Clear(){
        Close;
        if(mux !is null) mux.lock();
        scope(exit) {
            if(mux !is null) mux.unlock();
        }
        err_msg = null ;
        file    = null ;
    }
    
    char[] ErrorMessage(){
        if( _error_message !is null ) {
            return _error_message ;
        }
        return err_msg ;
    }
}

struct  vPlayer{
    
    static private iPlayer  ip ;
    static private Mutex mux  ;
    static private bool app_exit, is_playing ;
    static private ev_time_t start_time ;
    static private int  play_duration ;
    static private int  play_skip ;
    static private int  play_volume ;
    static private int  task_id ;
    static private int  is_task_play ;
    static public uint  update_counter ;
    
    // static private Thread  thread  ;
    static void Init() {
        mux = new Mutex;
        app_exit    = false ;
        /*
            thread = new Thread(&Run);
            thread.start ;
            */
    }
    
    static int getTaskId( bool skip_look = false ){
        int  _task_id ;
        if( !skip_look ) mux.lock();
        _task_id    = task_id ;
        if( !skip_look ) mux.unlock();
        return _task_id ;
    }
    
    static bool play(char[] _file, int skip, int duration, int _task_id , bool is_task = false ){
        mux.lock() ;
        scope(exit) {
            mux.unlock();
        }
    
        version(DEV) log!(__FILE__,__LINE__)(" play, task_id={}, _task_id={}", task_id, _task_id) ;
        
        if( is_playing ) {
            version(DEV) log!(__FILE__,__LINE__)("before play, stop old task_id={}", task_id);
            stop(true, true) ;
            Thread.sleep(0.2) ;
        }
        
        _vTask*  _vtask = null ;
        task_id = -1 ;
        
        if( _task_id > 0 ) {
            _vtask  = Task_List.ByID(_task_id, is_task) ;
            if( is_task ) {
                assert( _vtask !is null ) ;
                if( _task_id is task_id ) {
                    version(DEV) log!(__FILE__,__LINE__)(" play, already_stared task_id={}", task_id);
                    return true ;
                }
            } 
        }
        
        if( _vtask !is null ) {
            _vtask.isFire   = false ;
        }
        
        if( !ip.Open(_file) ) {
            version(DEV) log!(__FILE__, __LINE__)("_play: {}", ip.ErrorMessage  );
            return false ;
        }
        
        int _total_seconds  = cast(int)  ip.total_seconds ;
        
        update_counter  = 0 ;
        if( skip < 0 || skip >=  _total_seconds ){
            skip    = 0 ;
        }
        
        if( skip > 0 ) {
           ip.setPosition(skip);
            play_skip   = skip ;
        } else {
            play_skip   = 0 ;
        }
        
        play_duration   =  _total_seconds - skip ;
        
        if( duration !is 0 ) {
            if( play_duration > duration  ) {
                play_duration   = duration ;
            }
        }
        
        iTaskList.total_time.store(play_duration);
        iTaskList.passed_time.store(0);
        play_volume = 10 ;
        
        if( !ip.Play() ) {
            version(DEV) log!(__FILE__, __LINE__)("_play: {}", ip.ErrorMessage  );
            play_duration   = 0 ;
            play_skip   = 0 ;
            return false ;
        }
        
        is_task_play    = is_task ;
        
        start_time  = vTime.ev_time_now ;
        is_playing  = true ;
        
        if( _task_id > 0 ) {
            task_id = _task_id ;
        }
        if( _vtask !is null ) {
            _vtask.isFire   = true ;
        }
        
        iTaskList.async_started.run();
        return true  ;
    }
    
    static void pause(){
        mux.lock();
        task_id = -1 ;
        mux.unlock();
        iTaskList.async_paused.run();
    }
    
    static void stop(bool skip_lock = false, bool skip_update = false ){
        if( !skip_lock) mux.lock();
        scope (exit) {
            if( !skip_lock) mux.unlock();
        }
        ip.Clear() ;
        _vTask* _task    = Task_List.ByID(task_id);
        if( _task !is null ) {
            _task.isFire    = false ;
            if( is_task_play ){
                _task.isDone    = true ;
            }
        }
        task_id = -1 ;
        is_playing  = false ;
        is_task_play    = false ;
        play_duration   = 0 ;
        play_skip   = 0 ;
        if( !skip_update ) {
            iTaskList.async_stoped.run();
        }
    }
    
    static bool Exit(bool force = false ){
        bool is_exited  = false ;
        mux.lock();
        if( app_exit ) {
            is_exited   = true ;
        }
        ip.Clear;
        task_id = -1 ;
        app_exit    = true ;
         Sys.Exit();
        vLog.Exit();
        if( force ) {
            .exit(0);
        }
        mux.unlock();
        return is_exited ;
    }
    
    static void __update(bool skip_lock = false, bool use_async = false ){
        if( !skip_lock) mux.lock();
        scope (exit) {
            if( !skip_lock) mux.unlock();
        }
        
        if( update_counter >= int.max ) update_counter = 0 ;
        update_counter++;

        if( is_playing ) {
            auto _now_time  = vTime.ev_time_now  ;
            auto passed_time = _now_time - start_time ;
            if( ip.isStoped || passed_time >= play_duration  ) {
                version(DEV)  log!(__FILE__, __LINE__)("stoped,  task_id={} play_duration={} passed_time={}  ", task_id,  play_duration, passed_time);
                stop(true, true);
                if( use_async ) {
                    GShell.getDisplay.asyncExec(iTaskList.async_stoped);
                } else {
                    iTaskList.async_stoped.run() ;
                }
            } else {

                iTaskList.passed_time.store( cast(int) passed_time );
                
                if( use_async ) {
                    GShell.getDisplay.asyncExec(iTaskList.async_playing );
                } else {
                    iTaskList.async_playing.run() ;
                }
                if( update_counter % 44 is 0 ){
                    // version(DEV)  log!(__FILE__, __LINE__)("playing,  task_id={} play_duration={} passed_time={}:{}, current_seconds={},  seconds_left={}", task_id,  play_duration, passed_time , cast(int) passed_time , ip.current_seconds,  ip.seconds_left );
                }
            }
        }
    }
    /*
    static void Run(){
        while( !app_exit ) {
            Thread.sleep(0.1);
            mux.lock();
            __update(true, true );
            mux.unlock();
        }
        exit(true) ;
    }
    */
}

